// Copyright (c) 2021 InCore Semiconductors Pvt. Ltd.
// see LICENSE.incore for more details on licensing terms
/*
Author: Babu P S , babu.ps@incoresemi.com
Created on: Thursday 15 April 2021 11:58:16 AM IST
Description: Sets GPIO and prints Boot Information.
Based on the configuration mode selected, Jumps to the
dedicated locations.
*/
#include <stdio.h>
#include <boot.h>

typedef struct {
   uint8_t dplx : 1;
   uint8_t lsb  : 1;
   uint8_t mstr : 1;
   uint8_t cpha : 1;
   uint8_t cpol : 1;
} spi_setup;

//pointers to register
#define INCORESPI_REG_CR1       0x00
#define INCORESPI_REG_CR2       0x01
#define INCORESPI_REG_EN        0x02
#define INCORESPI_REG_SR        0x03
#define INCORESPI_REG_TX        0x04
#define INCORESPI_REG_RX        0x05
#define INCORESPI_REG_PSCR      0x09

// SOME CONSTANTS USED IN FLASH
#define RXNE                    1
#define TXE                     2
#define SPI_TX_FIFO_NF          8
#define SPI_BSY                 0x800
#define SPI_RX_CAPACITY         (20 + 4) //fifo_depth:20 and rx_reg: 4

// FLASH REGISTER COMMANDS
#define FL_STATUS               0x05000000
#define FL_FST_RD               0x0C000000
#define FL_DEV_ID               0x9F000000
#define WIP_STATE               0x03000000

#define SPI_CPHA(x)             (x << 0)
#define SPI_CPOL(x)             (x << 1)
#define SPI_MSTR(x)             (x << 2)
#define SPI_IS_RX_1ST(x)        (x << 6)
#define SPI_LSBFIRST(x)         (x << 7)
#define SPI_CLR_ST              (1 << 10)
#define SPI_DPLX(x)             (x << 14)
#define SPI_TOTAL_BITS_TX(x)    (x << 16)
#define SPI_TOTAL_BITS_RX(x)    (x << 24)

static void set_spi(volatile uint32_t *ctrl_base, unsigned address, uint32_t value){
    ctrl_base[address] = value;
}
static uint32_t get_spi(volatile uint32_t *ctrl_base, unsigned address){
    return ctrl_base[address];
}

void waitfor(unsigned int secs){
    unsigned int time = 0;
    while (time++ < secs){
        unsigned long tmpCntr = 1000;
        while (tmpCntr--);
    }
}

void set_bits_rx_tx(volatile uint32_t *ctrl_base, uint8_t tx_bits, uint8_t rx_bits){
    uint32_t sreg = get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_CR1);
    while((ctrl_base[INCORESPI_REG_SR] & RXNE)){ // Clear all the contents in Rx FIFO
      (void)get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_RX);
    }
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_CR1, ((sreg & 0x0000FFFF) | SPI_CLR_ST |
                            SPI_TOTAL_BITS_TX(tx_bits) | SPI_TOTAL_BITS_RX(rx_bits)));
}

void spi_init(uint8_t slave_select, spi_setup dml , uint16_t prescalar){
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_PSCR, prescalar);
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_CR1, (SPI_DPLX(dml.dplx) | SPI_CLR_ST
            | SPI_IS_RX_1ST(0) | SPI_LSBFIRST(dml.lsb)| SPI_MSTR(dml.mstr)
            | SPI_CPHA(dml.cpol) | SPI_CPHA(dml.cpha)));
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_CR2, 0);
    while((get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_SR) & RXNE)){ // Clear all the contents in Rx FIFO
      (void)get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_RX);
    }
}

uint32_t flash_read_cmd(uint32_t cmd){
    set_bits_rx_tx(CHROMITEM_SPI_BASE, 8 , 8);
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_TX, cmd);
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_EN, 1);
    while((get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_SR) & RXNE) != 0 ){ // Wait until RxR is available
        waitfor(50);
    }
    return get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_RX);
}

void flash_read_data(uint32_t addr, uint8_t rx_bits){
    set_bits_rx_tx(CHROMITEM_SPI_BASE, 48 , rx_bits);
    // command (8) + addr (32) + dummy(CR2NV[3:0] = 8)
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_TX, (FL_FST_RD | ((addr >> 8) & 0x00FFFFFF)));
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_TX, ((addr << 24) & 0xFF000000));
    set_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_EN, 1);
    while((get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_SR) & SPI_BSY) != 0);
}

void jump_to(Addr* jump_addr)
{
    volatile register unsigned long* a0 asm("a0") = (unsigned long*) jump_addr;
    asm volatile("fence.i");
    asm volatile("jr a0");
}

void main(){
    uint32_t read_address = FL_BOOTADDR;  // read from this address
    uint32_t* bram_address = (uint32_t*) CHROMITEM_DDR_BASE;
    uint32_t sze;
    uint32_t arch = 0;

    //Set the output_en of the various IOs as per the FPGA configuration.
    //The GPIOs 0-15 should be configured as inputs.
    volatile short* temp1_gpio_out_en= (short*) CHROMITEM_GPIO_OUTPUT_EN;
    *temp1_gpio_out_en= 0x0000;

    //The GPIOs 16-19 should be configured as outputs
    volatile char* temp2_gpio_out_en= (char*) (CHROMITEM_GPIO_OUTPUT_EN + 2);
    *temp2_gpio_out_en= 0xF;

    asm volatile(
             "csrr %[arch], marchid\n"
             :
             [arch]
             "=r"
             (arch)
            );

    printf("%s\nINCORE SEMICONDUCTORS Pvt. Ltd. \n",bootlogo);
    printf("\n\t%s\n",soc_name);
    printf("Booting hart 0 \n");
    printf("%s",VERSIONVAL);
    printf("%s",BUILDVAL);
    printf("%s",copyright);
    printf("%s",license);
    printf("\n");
    printf("Supported ISA: RV64IMACSU.\n");
    printf("Processor Arch ID: %x.\n", arch);

    if(boot_mode()==0) {
        printf("\n >>> Entering Debug Mode.\n\n");
        asm volatile("ebreak");
    }
    else if (boot_mode() == 1) {
        printf("\n>>> Booting from on-chip memory at 0x%x.\n\n",CHROMITEM_OCM_BASE);
        jump_to(ocm_base);
    }
    else if(boot_mode() == 2){
        // duplex disabled, Tx first in Half duplex, Controller(master), CPHA, CPOL
        spi_setup dml = {0, 0, 1, 0, 0};
        spi_init(0, dml, CHROMITEM_SPI_PRESCALAR);
        while((flash_read_cmd(FL_STATUS) & WIP_STATE) != 0);
        uint8_t mfg_id = ((flash_read_cmd(FL_DEV_ID) >> 24) & 0xFF) ;
        if(1 == mfg_id ){
            while((flash_read_cmd(FL_STATUS) & WIP_STATE) != 0);
            flash_read_data(read_address, 32);
            sze = get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_RX);
            if(sze == 0 || sze == 0xFFFFFFFF){
                printf("No content to boot \n");
                printf("\n >>> Entering Debug Mode.\n\n");
                asm volatile ("ebreak");
            }
    	    read_address += 4;
            while((flash_read_cmd(FL_STATUS) & WIP_STATE) != 0);

        //  Performs read at 40 bits overhead per SPI_RX_CAPACITY bytes
            unsigned int i = 0;
            register uint8_t cur_count = SPI_RX_CAPACITY;
            while(i < sze){
                cur_count =  ((i + SPI_RX_CAPACITY) < sze) ? SPI_RX_CAPACITY : (sze -i) ;
                while((flash_read_cmd(FL_STATUS) & WIP_STATE) != 0);
                flash_read_data(read_address, (cur_count << 3));
                read_address += cur_count;
                while((get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_SR) & RXNE) != 0){ // Wait until RxR is available
                    *(bram_address++) = get_spi(CHROMITEM_SPI_BASE, INCORESPI_REG_RX);
                }
                i +=cur_count;
            }
        }else{
            printf("Wrong device id 0x%x\n", mfg_id);
            asm volatile ("ebreak");
        }
        printf("\n>>> Booting from DDR memory at 0x%x.\n\n",CHROMITEM_DDR_BASE);
        spi_init(0, dml, CHROMITEM_SPI_PRESCALAR);
        jump_to(ddr_base);
    }
    asm volatile ("ebreak");
}
